package winusb

import (
	"fmt"
	"strings"
	"syscall"
	"unsafe"
)


type SetupDi struct {
	setupapi *syscall.DLL
	
	setupDiGetClassDevs *syscall.Proc
	setupDiDestroyDeviceInfoList *syscall.Proc

	setupDiEnumDeviceInfo *syscall.Proc
	setupDiGetDeviceInstanceId *syscall.Proc

	setupDiEnumDeviceInterfaces *syscall.Proc
	setupDiGetDeviceInterfaceDetail *syscall.Proc
}


func NewSetupDi() (* SetupDi, error) {

	var sdi SetupDi

	sdll, err := syscall.LoadDLL( "SetupAPI.dll" )
	if err != nil {
		return nil, err 
	}

	sdi.setupapi = sdll
	sdi.setupDiGetClassDevs, err = sdll.FindProc( "SetupDiGetClassDevsW" )
	if err != nil {
		return nil, err
	}

	sdi.setupDiDestroyDeviceInfoList, err = sdll.FindProc( "SetupDiDestroyDeviceInfoList" )
	if err != nil {
		return nil, err
	}

	sdi.setupDiEnumDeviceInfo, err = sdll.FindProc( "SetupDiEnumDeviceInfo" )
	if err != nil {
		return nil, err
	}

	sdi.setupDiGetDeviceInstanceId, err = sdll.FindProc( "SetupDiGetDeviceInstanceIdW" )
	if err != nil {
		return nil, err
	}	

	sdi.setupDiEnumDeviceInterfaces, err = sdll.FindProc( "SetupDiEnumDeviceInterfaces" )
	if err != nil {
		return nil, err
	}

	sdi.setupDiGetDeviceInterfaceDetail, err = sdll.FindProc( "SetupDiGetDeviceInterfaceDetailW" )
	if err != nil {
		return nil, err
	}

	return &sdi, nil
}


type SP_DEVICE_INTERFACE_DATA struct {
	cbsiz  	uint32
	guid   	syscall.GUID
	flags  	uint32
	rsved	uintptr
}



func (sdi * SetupDi)GetDetailPath( hdi uintptr, did uintptr ) (string, error) {
	
	var bret  uintptr
	var reqlen uint32

	
	bret, _, err := sdi.setupDiGetDeviceInterfaceDetail.Call( hdi, did, 0, 0, uintptr( unsafe.Pointer(&reqlen)), 0 )
	if bret != 0 {
		// 结果必须是 false.
		return "", syscall.ERROR_OPERATION_ABORTED
	}

			
	if eno, ok := err.(syscall.Errno); ok {

		if eno != syscall.ERROR_INSUFFICIENT_BUFFER  {
			fmt.Println( "eno =", uint32(eno) )
			return "", err
		}
		
	}  else {
		return "", err
	}

	//
	tary := make( []uint16, (reqlen/2 + 1) )
	tary[0] = 8;
	tary[1] = 0;
	bret, _, err = sdi.setupDiGetDeviceInterfaceDetail.Call( hdi, did, uintptr( unsafe.Pointer(&tary[0])), uintptr(reqlen), uintptr( unsafe.Pointer(&reqlen)), 0 )
	if bret == 0 {
		return "", err
	}

	//
	upth := syscall.UTF16ToString( tary[2:] )
	return upth, nil
}


func (sdi * SetupDi)ScanUsbDev( vid uint16, pid uint16 ) (string, error) {
	
	var bret uintptr
	var sdid SP_DEVICE_INTERFACE_DATA
	var patn string

	patn = fmt.Sprintf( "\\\\?\\usb#vid_%04x&pid_%04x#", vid, pid )

	// GUID_DEVINTERFACE_USB_DEVICE
	ugid := syscall.GUID{ Data1: 0xa5dcbf10, Data2:0x6530, Data3 :0x11d2, Data4: [8]byte{ 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed } }

	// DIGCF_PRESENT = 0x00000002
	// DIGCF_DEVICEINTERFACE 0x00000010
	hdi, _, err := sdi.setupDiGetClassDevs.Call(  uintptr( unsafe.Pointer(&ugid) ), 0, 0, uintptr(0x12) )
	if hdi == uintptr( syscall.InvalidHandle ) {
		return "", err
	}

	defer sdi.setupDiDestroyDeviceInfoList.Call( hdi )

	//
	for i:=0; i<256; i++ {

		sdid.cbsiz = uint32( unsafe.Sizeof(sdid) )
		bret, _, err = sdi.setupDiEnumDeviceInterfaces.Call( hdi, 0, uintptr( unsafe.Pointer(&ugid) ), uintptr(i), uintptr( unsafe.Pointer(&sdid)) )
		if bret == 0 {
			// fmt.Println( "index =", i )
			return "", err
		}
		
		upth, err := sdi.GetDetailPath( hdi, uintptr( unsafe.Pointer(&sdid) ) )
		if err != nil {
			return "", err
		}

		// 
		if strings.HasPrefix( upth, patn )  {
			return upth, nil
		}

	}

	return "", syscall.ERROR_NOT_FOUND
}


type SP_DEVINFO_DATA struct  {
  cbSize  uint32
  clsGuid  syscall.GUID
  devInst  uint32
  rsvd  uintptr
}




func (sdi * SetupDi)GetInstanceID( hdi uintptr, sdd uintptr ) (string, error) {
	var bret  uintptr
	var reqlen uint32

	
	bret, _, err := sdi.setupDiGetDeviceInstanceId.Call( hdi, sdd, 0, 0, uintptr( unsafe.Pointer(&reqlen)) )
	if bret != 0 {
		// 结果必须是 false.
		return "", syscall.ERROR_OPERATION_ABORTED
	}

	if eno, ok := err.(syscall.Errno); ok {

		if eno != syscall.ERROR_INSUFFICIENT_BUFFER  {
			fmt.Println( "eno =", uint32(eno) )
			return "", err
		}
		
	}  else {
		return "", err
	}

	//
	tary := make( []uint16, (reqlen + 1) )
	bret, _, err = sdi.setupDiGetDeviceInstanceId.Call( hdi, sdd, uintptr( unsafe.Pointer(&tary[0])), uintptr(reqlen + 1), 0 )
	if bret == 0 {
		return "", err
	}

	//
	upth := syscall.UTF16ToString( tary[:] )
	return upth, nil
}


/*
port, serial, ???
*/
func (sdi * SetupDi)EnumUsbDev( vid uint16, pid uint16 ) ([]string, error) {

	var bret uintptr
	var sdd SP_DEVINFO_DATA
	var patn string
	var rult []string
	
	// USB\VID_0BDA&PID_4853
	patn = fmt.Sprintf( "USB\\VID_%04X&PID_%04X\\", vid, pid )
	
	// GUID_DEVINTERFACE_USB_DEVICE
	ugid := syscall.GUID{ Data1: 0xa5dcbf10, Data2:0x6530, Data3 :0x11d2, Data4: [8]byte{ 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed } }

	// DIGCF_PRESENT = 0x00000002
	// DIGCF_DEVICEINTERFACE 0x00000010
	hdi, _, err := sdi.setupDiGetClassDevs.Call(  uintptr( unsafe.Pointer(&ugid) ), 0, 0, uintptr(0x12) )
	if hdi == uintptr( syscall.InvalidHandle ) {
		return nil, err
	}

	defer sdi.setupDiDestroyDeviceInfoList.Call( hdi )

	//
	for i:=0; i<256; i++ {

		sdd.cbSize = uint32( unsafe.Sizeof(sdd) )
		bret, _, err = sdi.setupDiEnumDeviceInfo.Call( hdi, uintptr(i), uintptr( unsafe.Pointer(&sdd)) )
		if bret == 0 {

			if erid, ok := err.(syscall.Errno); ok {
				// ERROR_NO_MORE_ITEMS, 259(0x103)
				if uintptr(erid) == 259 {
					return rult, nil
				}
			}
		
			return nil, err
		}
		
		// 
		isid, err := sdi.GetInstanceID( hdi, uintptr( unsafe.Pointer(&sdd)) )
		if err != nil {
			return nil, err
		}
		
		// filter by pid.
		if !strings.HasPrefix( isid, patn )  {
			continue
		}
		
		//
		rult = append( rult, isid )
		// fmt.Printf( "%v: %s\n", i, isid )
	}
	
	return rult, nil
}


/*
使用 InstanceID 查找 SP_DEVINFO_DATA.
再使用 SP_DEVINFO_DATA 来获取 DetailPath.
*/
func (sdi * SetupDi)TransFullPath( insid string ) (string, error) {

	var bret uintptr
	var sdd SP_DEVINFO_DATA
	var sdid SP_DEVICE_INTERFACE_DATA
	
	// GUID_DEVINTERFACE_USB_DEVICE
	ugid := syscall.GUID{ Data1: 0xa5dcbf10, Data2:0x6530, Data3 :0x11d2, Data4: [8]byte{ 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed } }

	// DIGCF_PRESENT = 0x00000002
	// DIGCF_DEVICEINTERFACE 0x00000010
	hdi, _, err := sdi.setupDiGetClassDevs.Call(  uintptr( unsafe.Pointer(&ugid) ), 0, 0, uintptr(0x12) )
	if hdi == uintptr( syscall.InvalidHandle ) {
		return "", err
	}

	defer sdi.setupDiDestroyDeviceInfoList.Call( hdi )

	//
	for i:=0; i<256; i++ {

		sdd.cbSize = uint32( unsafe.Sizeof(sdd) )
		bret, _, err = sdi.setupDiEnumDeviceInfo.Call( hdi, uintptr(i), uintptr( unsafe.Pointer(&sdd)) )
		if bret == 0 {
			return "", err
		}
		
		// 
		isid, err := sdi.GetInstanceID( hdi, uintptr( unsafe.Pointer(&sdd)) )
		if err != nil {
			return "", err
		}
		
		//
		if isid == insid {
			break
		}
	}
	
	//
	sdid.cbsiz = uint32( unsafe.Sizeof(sdid) )
	bret, _, err = sdi.setupDiEnumDeviceInterfaces.Call( hdi, uintptr( unsafe.Pointer(&sdd)), uintptr( unsafe.Pointer(&ugid) ), uintptr(0), uintptr( unsafe.Pointer(&sdid)) )
	if bret == 0 {
		// fmt.Println( "index =", i )
		return "", err
	}
	
	upth, err := sdi.GetDetailPath( hdi, uintptr( unsafe.Pointer(&sdid) ) )
	if err != nil {
		return "", err
	}
	
	return upth, nil
}



